package hip.util.id;

/**
 * Created by jerry on 2016/7/20.
 */

import org.apache.log4j.Logger;

import java.util.HashSet;

/**
 * Snowflake
 */
public class IdWorker {
    public static Logger logger = Logger.getLogger(IdWorker.class);
    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;//机器id所占的位数 
    private final long datacenterIdBits = 5L;//数据标识id所占的位数 
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);//支持的最大机器id，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);//支持的最大数据标识id，结果是31 
    private final long sequenceBits = 12L;//序列在id中占的位数
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    private final long[] byteTable = createLookupTable();
    private static final long HSTART = 0xBB40E64DA205B064L;
    private static final long HMULT = 7664345821815920749L;

    public IdWorker() {
    }

    /**
     * 兼容2.5.0版本
     *
     * @param workerId
     */
    @Deprecated
    public IdWorker(long workerId) {
        if ((workerId > 15L) || (workerId < 0L)) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", new Object[]{Long.valueOf(15L)}));
        }
        this.workerId = workerId;
    }


    public IdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    //只能用于独立部署的机器，分布式的机器可能时间不一致会重复
    public synchronized long nextId1() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        Long num = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
        return num;
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected long timeGen() {
        return System.currentTimeMillis();
    }

    public long nextId() {
        String uuid = UUIDUtil.getUUID();
        String abs = ((Long) Math.abs(hashCode(uuid))).toString();
        //这里截断的目的是因为16位长的数字当数据传到浏览器之后，js不支持比16位还长的数字
        if (abs.length() > 16) {
            abs = abs.substring(0, 16);
        }
        return Long.parseLong(abs);
    }

    public long hashCode(CharSequence cs) {
        long h = HSTART;
        final long hmult = HMULT;
        final long[] ht = byteTable;
        final int len = cs.length();
        for (int i = 0; i < len; i++) {
            char ch = cs.charAt(i);
            h = (h * hmult) ^ ht[ch & 0xff];
            h = (h * hmult) ^ ht[(ch >>> 8) & 0xff];
        }
        return h;
    }

    private final long[] createLookupTable() {
        long[] byteTable = new long[256];
        long h = 0x544B2FBACAAF1684L;
        for (int i = 0; i < 256; i++) {
            for (int j = 0; j < 31; j++) {
                h = (h >>> 7) ^ h;
                h = (h << 11) ^ h;
                h = (h >>> 10) ^ h;
            }
            byteTable[i] = h;
        }
        return byteTable;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void main(String[] args) throws Exception {
        HashSet<Long> set = new HashSet();
        for (int i = 0; i < 100; i++) {
            long id = new IdWorker().nextId();
            set.add(id);
            logger.info(id + "---" + (id + "").length());
            if (set.size() != (i + 1)) {
                logger.info(i);
                break;
            }
        }
    }
}